package per.hnvcam.genetic.algorithm.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import per.hnvcam.common.OrderedSet;
import per.hnvcam.genetic.algorithm.Chromosome;
import per.hnvcam.genetic.algorithm.FitnessCalculator;
import per.hnvcam.genetic.algorithm.GeneticAlgorithm;
import per.hnvcam.genetic.algorithm.GeneticOperations;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: Vi Cam
 * Date: 25/12/2011
 * Time: 22:57
 * To change this template use File | Settings | File Templates.
 */
@Service("GeneticAlgorithm")
public class GeneticAlgorithmImpl implements GeneticAlgorithm {
	
   @Autowired
   private GeneticOperations geneticOperations;
   
   @Autowired
   private FitnessCalculator fitnessCalculator;
   
   private List<Chromosome> initialPopulation;
   private List<Long> domain;
   private int generationLimit;
   private double alpha;
   private int loopLimit;
   
   public GeneticAlgorithmImpl() {
      domain = new LinkedList<Long>();
      loopLimit = 10;
      alpha = 0.7;
      generationLimit = 20;
   }
   
   @Override
   public void setInitialPopulation(List<Chromosome> initialPopulation) {
      this.initialPopulation = initialPopulation;
      Set<Long> tempSet = new HashSet<Long>();
      for (Chromosome c : initialPopulation) {
         for (Long l : c.getGenes()) {
            if (!tempSet.contains(l)) {
               domain.add(l);
               tempSet.add(l);
            }
         }
      }
   }

   @Override
   public void setGenerationLimit(int generationLimit) {
      this.generationLimit = generationLimit;
   }
   
   private double getInitialTemperature() {
      double result = 0;
      double averageFitness = 0;
      for (Chromosome c : initialPopulation) {
         averageFitness += fitnessCalculator.getFitness(c); 
      }
      averageFitness *= 1 / initialPopulation.size();
      
      for (Chromosome c : initialPopulation) {
         result += Math.pow(fitnessCalculator.getFitness(c) - averageFitness, 2);
      }
      result = Math.pow(result * 1 / initialPopulation.size(), 0.5);
      return result;
   }
   
   private List<Chromosome> getLatestGeneration() {
      List<Chromosome> currentPopulation = new ArrayList<Chromosome>();
      currentPopulation.addAll(initialPopulation);
      int currentGeneration = 1;
      double currentThermal = getInitialTemperature();
      while (currentGeneration < generationLimit) {
         currentThermal *= alpha;
         Chromosome [] selectedParents = geneticOperations.applySelection(currentPopulation);
         Chromosome [] crossoveredChildren = geneticOperations.applyCrossover(selectedParents[0], selectedParents[1]);
         Chromosome [] survivals = geneticOperations.applyReplacement(selectedParents, crossoveredChildren);
         if (isNoNewGenerationAccepted(survivals, selectedParents)) {
            Chromosome [] mutatedChildren = new Chromosome[2];
            mutatedChildren[0] = mutate(selectedParents[0]);
            mutatedChildren[1] = mutate(selectedParents[1]);
            survivals = geneticOperations.applyReplacement(selectedParents, mutatedChildren);
         }
         currentPopulation.remove(selectedParents[0]);
         currentPopulation.remove(selectedParents[1]);
         currentPopulation.add(survivals[0]);
         currentPopulation.add(survivals[1]);
         currentGeneration++;
      }
      return currentPopulation;
   }
   
   private Chromosome mutate(Chromosome chromosome) {
      int mutatedPosition = (int) (Math.random() * chromosome.getGenes().size());
      long mutatedValue = Long.MIN_VALUE;
      int loopCount = 0;
      do {
         mutatedValue = domain.get((int) (Math.random() * domain.size()));
      } while (chromosome.getGenes().contains(mutatedValue) && loopCount < loopLimit);
      return geneticOperations.applyMutation(chromosome, mutatedPosition, mutatedValue);
   }

   private boolean isNoNewGenerationAccepted(Chromosome[] survivals, Chromosome[] selectedParents) {
      return (survivals[0].equals(selectedParents[0]) && survivals[1].equals(selectedParents[1])) ||
            (survivals[1].equals(selectedParents[0]) && survivals[0].equals(selectedParents[1]));
   }

   @Override
   public Chromosome getFitnessChromosome() {
      OrderedSet<Chromosome> sortedPopulation = geneticOperations.sortPopulationByFitness(getLatestGeneration()); 
      return sortedPopulation.first();
   }

   @Override
   public void setDecreaseTemperatureScale(double scale) {
      this.alpha = scale;      
   }
}
